import os
import time
import datetime
import cPickle as pickle
import struct

import cache, chunk
from util import config

# Constsants used
LSN_NOT_CALCULATED = -1
NO_GOOD = -1

import threading
from util.decorators import *

def add_days(time, days):
    return time + days * 24 * 60 * 60

class Header:
    
    def __init__(self, url, msg_type, src, dests, meta, csize, expiration_delta, explicit_mid):
        """
            Creates a header for a message.
            
            This method generates a message id which is an integer.
            
            @param url: The absolute path of the file backing the message on the local device
            @type url: string
            @param msg_type: The type of the message to send
            @type msg_type: integer
            @param src: The ID of the node that sent this message
            @type src: integer
            @param dests: The destinations of this message. A list of node IDs (integers). Empty if this is broadcast.
            @type dests: List(integer)
            @param meta: A dictionary of arbitrary metadata associated with this message. 
                        This tends to be set by the application creating messages.
            @type meta: dictionary
            @param csize: The chunk size, in bytes, to use. The default is set in the async.config file. (Something like 1000)
            @type csize: integer
            @param expiration_delta: The time before expiration
            @type expiration_delta: integer
        """
        self.url = url
        self.src = src
        self.dests = dests
        self.meta = meta
        self.csize = csize
        self.created = time.time()
        # 1 minute, to test expiration
        self.expires = time.time() + expiration_delta
        self.received = -1
        self.msg_type = msg_type
        self.lsn = LSN_NOT_CALCULATED
        self.delivered = False
        self.hops = []
        self.fname = url.replace(config.get('async.folders', self.msg_type), '')
        if explicit_mid:
            self.mid = int(explicit_mid)
        else:
            self.mid = int(hash(str(self.fname) + str(self.src) + str(self.dests) + str(self.csize) + str(self.created)))
        # ttl? 
        # self.ttl = #
        # self.mid = str(1)

    def get_file_path(self):
        # Gets a path, given the message type and the filename in the header
        return config.get('async.folders', self.msg_type)  + '/' + self.fname

    def raw(self):
        pickled = pickle.dumps(self, pickle.HIGHEST_PROTOCOL)
        length = len(pickled)
        return struct.pack('i' + str(length) + 's', length, pickled)

    def am_sender(self, my_id):
        return self.src == my_id
    
    def is_expired(self):
        return time.time() > self.expires
    
    @staticmethod
    def from_raw(raw):
        length = struct.unpack('i', raw[:4])[0]
        pickled = struct.unpack(str(length) + 's', raw[4:])[0]
        msg = pickle.loads(pickled)
        # Set the received time here, as we must have just received this
        msg.received = time.time()
        return msg


    
def create_new(url, msg_type, src, dests, meta, csize, expiration_delta, explicit_mid):
    """
        Used when sending a new message.
        Creates a new header, message, and initializes the chunks for this message.
    """
    header = Header(url, msg_type, src, dests, meta, csize, expiration_delta, explicit_mid)
    msg = Message(header)
    msg._create_chunks()
    return msg

def create_from_header(header):
    """
        Used when assembling an incoming message.
        Creates a message from a header, but does not initialize the chunks.
    """
    msg = Message(header)
    return msg

def depersist(url):
    f = open(url, mode='rb')
    msg = pickle.load(f)
    f.close()
    return msg

def get_persist_path(header):
    return config.get('async.folders', 'active-root')  + str(header.mid) + '.message'

class Message:
    
    lock = threading.Lock()

    def __init__(self, header):
        self.header = header
        self.__last_good = NO_GOOD
        self.__chunks = {}
        self.__next = -1


    def _create_chunks(self):
        assert self.header is not None
        assert os.path.exists(self.header.url)

        fsize = os.stat(self.header.url).st_size
        seq = 0
        
        while fsize > 0:
            self.__chunks[seq] = True
            fsize -= self.header.csize
            seq +=1

        lsn = seq-1
        self.header.lsn = lsn
        self.__last_good = self.header.lsn
        
    @synchronized(lock)
    def get_chunk(self, seq):
        return cache.get_outgoing().get_chunk(self.header, seq)
    
    @synchronized(lock)
    def put_chunk(self, chunk):
        cache.get_incoming().put_chunk(self.header, chunk)
        self.__chunks[chunk.seq] = True
        
    @synchronized(lock)
    def get_incomplete_seqs(self):  
        incomplete = []
        # Range(n) goes 0->n-1.
        # We need range(0...n), so add 1.
        for i in xrange(self.header.lsn + 1):
            if not i in self.__chunks:
                incomplete.append(i)
        return incomplete

    def is_complete(self):
        return len(self.get_incomplete_seqs()) == 0

    @synchronized(lock)
    def decode(self):
        # This just renames the cached file to the target file
        cache.get_incoming().msg_complete(self.header)
        # Also persists the file to disk
        self.__persist()
        assert os.path.exists(self.header.get_file_path())
    
    # FOR INTERNAL USE ONLY. Need to avoid some 
    # locking headaches.
    def __persist(self):
        path = get_persist_path(self.header)
        f = open(path, mode='wb')
        pickle.dump(self, f, pickle.HIGHEST_PROTOCOL)
        f.close()
        
    @synchronized(lock)
    def persist(self):
        self.__persist()
    
    @synchronized(lock)
    def destroy_persisted(self):
        path = get_persist_path(self.header)
        if os.path.exists(path):
            os.remove(path)
        assert not os.path.exists(path)

    def is_expired(self):
        return self.header.is_expired()
    
    def is_for(self, dest):
        return len(self.header.dests) == 0 or dest in self.header.dests

    def is_for_any(self, dests):
        if len(self.header.dests) == 0:
            return True
        else:
            for dest in dests:
                if dest in self.header.dests:
                    return True
            return False

    def is_for_all(self, dests):
        if len(self.header.dests) == 0:
            return True
        else:
            if len(dests) == len(self.header.dests):
                for dest in dests:
                    if dest not in self.header.dests:
                        return False
                return True
        return False
    
    def __iter__(self):
        return self
    
    def next(self):
        self.__next += 1
        if self.__next > self.header.lsn:
            raise StopIteration
        elif self.__next == self.header.lsn:
            c = chunk.EndChunk(self.header.mid, self.__next)
        else:
            c = self.get_chunk(self.__next)
        return c

